// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: wsfuyibing <682805@qq.com>
// Date: 2024-12-05

package filter

import (
	"context"
	"encoding/json"
	"encoding/xml"
	"gitee.com/go-libs/log"
	"gitee.com/gomq/sr/errs"
)

type (
	// Filter is a component used to check given body is matched on given
	// expression.
	Filter interface {
		// GetError
		// return the first error.
		GetError() error

		// GetErrors
		// returns an error list.
		GetErrors() []error

		// GetExpression
		// return filter origin expression.
		GetExpression() Expression

		// GetFormat
		// return filter formatter.
		GetFormat() Format

		// HasError
		// return true if error occurred when parse done.
		HasError() bool

		// Match
		// given body is matched on expression.
		Match(ctx context.Context, data []byte) (matched bool)

		// MatchString
		// given string is matched on expression.
		MatchString(ctx context.Context, str string) (yes bool)

		// Query
		// returns a filter query expression like sql.
		//
		//   return `WHERE key == 1 OR (key == 2 AND value IN ("val", "value"))`
		Query() string
	}

	filter struct {
		Err        error      `json:"-"`
		ErrExists  bool       `json:"-"`
		ErrList    []error    `json:"-"`
		Groups     []Group    `json:"-"`
		Expression Expression `json:"Expression,omitempty"`
		Format     Format     `json:"Format,omitempty"`

		query string
	}
)

// NewFilter
// creates a filter instance with given format and expression.
func NewFilter(format Format, expression Expression) Filter {
	o := &filter{Format: format, Expression: expression}
	o.ErrList = make([]error, 0)
	o.Groups = make([]Group, 0)

	o.parse()
	o.ready()
	o.buildQuery()
	return o
}

// + Interfaces

func (o *filter) GetError() error                                  { return o.Err }
func (o *filter) GetErrors() []error                               { return o.ErrList }
func (o *filter) GetFormat() Format                                { return o.Format }
func (o *filter) GetExpression() Expression                        { return o.Expression }
func (o *filter) HasError() bool                                   { return o.ErrExists }
func (o *filter) Match(ctx context.Context, body []byte) bool      { return o.match(ctx, body) }
func (o *filter) MatchString(ctx context.Context, str string) bool { return o.Match(ctx, []byte(str)) }
func (o *filter) Query() string                                    { return o.query }

// + Match

func (o *filter) match(ctx context.Context, body []byte) (yes bool) {
	var (
		data = make(map[string]any)
		err  error
	)

	// Unmarshal input body as map data.
	switch o.Format {
	case FormatJson:
		if json.Unmarshal(body, &data) != nil {
			err = errs.ErrFilterInvalidBodyForJson
		}
	case FormatXml:
		if xml.Unmarshal(body, &data) != nil {
			err = errs.ErrFilterInvalidBodyForXml
		}
	default:
		err = errs.ErrFilterInvalidBodyType
	}

	// Check error if given body type not supported.
	if err != nil {
		log.Errorfc(ctx, `[filter] skipped: format="%s", error="%v"`, o.Format, err)
		return
	}

	// Iterate condition groups, Break loop if true returned by any group.
	for _, g := range o.Groups {
		if yes = g.Match(ctx, data); yes {
			break
		}
	}

	// End filter.
	log.Infofc(ctx, `[filter] match result: success="%v"`, yes)
	return
}

// + Access

func (o *filter) buildQuery() {
	var (
		c  = ""
		ca = " OR "
	)
	for _, g := range o.Groups {
		o.query += c + g.Query()
		c = ca
	}
}

func (o *filter) parse() {
	// Format can not be empty.
	if o.Format.Undefined() {
		o.ErrList = append(o.ErrList, errs.ErrFilterFormatNotSpecified)
		return
	}

	// Expression can not be empty.
	if o.Expression.Undefined() {
		o.ErrList = append(o.ErrList, errs.ErrFilterExpressionNotSpecified)
		return
	}

	// Parse group.
	for _, str := range o.Expression.Split() {
		o.parseGroup(str)
	}
}

func (o *filter) parseGroup(str string) {
	g := NewGroup(str)
	if g.HasError() {
		o.ErrList = append(o.ErrList, g.GetErrors()...)
	} else {
		o.Groups = append(o.Groups, g)
	}
}

func (o *filter) ready() {
	if len(o.ErrList) > 0 {
		o.Err = o.ErrList[0]
		o.ErrExists = true
	} else if len(o.Groups) == 0 {
		o.Err = errs.ErrFilterNoAnyCondition
		o.ErrList = append(o.ErrList, o.Err)
		o.ErrExists = true
	}
}
